src/switchroot/ostree-mount-util.h \
src/switchroot/ostree-remount.c \
$(NULL)
-ostree_remount_CPPFLAGS = $(AM_CPPFLAGS) -Isrc/switchroot
+ostree_remount_CPPFLAGS = $(AM_CPPFLAGS) $(OT_INTERNAL_GIO_UNIX_CFLAGS) -Isrc/switchroot -I$(srcdir)/libglnx
+ostree_remount_LDADD = $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) libglnx.la
if BUILDOPT_SYSTEMD
ostree_prepare_root_CPPFLAGS += -DHAVE_SYSTEMD=1
OstreeSysroot
ostree_sysroot_new
ostree_sysroot_new_default
+ostree_sysroot_initialize
ostree_sysroot_get_path
ostree_sysroot_load
ostree_sysroot_load_if_changed
ostree_sysroot_lock_finish
ostree_sysroot_unlock
ostree_sysroot_unload
+ostree_sysroot_set_mount_namespace_in_use
+ostree_sysroot_is_booted
ostree_sysroot_get_fd
ostree_sysroot_ensure_initialized
ostree_sysroot_get_bootversion
ConditionKernelCommandLine=ostree
OnFailure=emergency.target
Conflicts=umount.target
-After=-.mount
+# Run after core mounts
+After=-.mount var.mount
After=systemd-remount-fs.service
+# But we run *before* most other core bootup services that need write access to /etc and /var
Before=local-fs.target umount.target
-# Other early boot units that need to write to /var
Before=systemd-random-seed.service plymouth-read-write.service systemd-journal-flush.service
-# tmpfiles.d usually needs write access to a few places
Before=systemd-tmpfiles-setup.service
[Service]
/* Add new symbols here. Release commits should copy this section into -released.sym. */
LIBOSTREE_2019.7 {
+ ostree_sysroot_initialize;
+ ostree_sysroot_is_booted;
+ ostree_sysroot_set_mount_namespace_in_use;
} LIBOSTREE_2019.6;
/* Stub section for the stable release *after* this development one; don't
#ifdef HAVE_LIBMOUNT
#include <libmount.h>
#endif
+#include <sys/statvfs.h>
#include <stdbool.h>
#include "otutil.h"
"[Unit]\n"
"Documentation=man:ostree(1)\n"
"ConditionKernelCommandLine=!systemd.volatile\n"
- /* We need /sysroot mounted writable first */
- "After=ostree-remount.service\n"
"Before=local-fs.target\n"
"\n"
"[Mount]\n"
OstreeRepo *repo = ostree_sysroot_repo (sysroot);
const guint depth = 0; /* Historical default */
+ if (!_ostree_sysroot_ensure_writable (sysroot, error))
+ return FALSE;
+
/* Hold an exclusive lock by default across gathering refs and doing
* the prune.
*/
GError **error)
{
g_return_val_if_fail (OSTREE_IS_SYSROOT (self), FALSE);
- g_return_val_if_fail (self->loaded, FALSE);
+ g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, FALSE);
+
+ if (!_ostree_sysroot_ensure_writable (self, error))
+ return FALSE;
if (!cleanup_other_bootversions (self, cancellable, error))
return glnx_prefix_error (error, "Cleaning bootversions");
#define OSTREE_DEPLOYMENT_FINALIZING_ID SD_ID128_MAKE(e8,64,6c,d6,3d,ff,46,25,b7,79,09,a8,e7,a4,09,94)
#endif
+static gboolean
+is_ro_mount (const char *path);
+
/*
* Like symlinkat() but overwrites (atomically) an existing
* symlink.
GCancellable *cancellable,
GError **error)
{
+ if (!_ostree_sysroot_ensure_writable (sysroot, error))
+ return FALSE;
+
GLNX_AUTO_PREFIX_ERROR ("Writing out origin file", error);
GKeyFile *origin =
new_origin ? new_origin : ostree_deployment_get_origin (deployment);
GCancellable *cancellable,
GError **error)
{
- g_assert (self->loaded);
+ g_assert (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED);
+
+ if (!_ostree_sysroot_ensure_writable (self, error))
+ return FALSE;
/* Dealing with the staged deployment is quite tricky here. This function is
* primarily concerned with writing out "finalized" deployments which have
if (boot_was_ro_mount)
{
- /* TODO: Use new mount namespace. https://github.com/ostreedev/ostree/issues/1265 */
if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
return glnx_throw_errno_prefix (error, "Remounting /boot read-write");
}
/* Note equivalent of try/finally here */
gboolean success = write_deployments_bootswap (self, new_deployments, opts, bootloader,
&syncstats, cancellable, error);
- /* Below here don't set GError until the if (!success) check */
- if (boot_was_ro_mount)
+ /* Below here don't set GError until the if (!success) check.
+ * Note we only bother remounting if a mount namespace isn't in use.
+ * */
+ if (boot_was_ro_mount && !self->mount_namespace_in_use)
{
if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_RDONLY | MS_SILENT, NULL) < 0)
{
GCancellable *cancellable,
GError **error)
{
+ if (!_ostree_sysroot_ensure_writable (self, error))
+ return FALSE;
+
g_autoptr(OstreeDeployment) deployment = NULL;
if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv,
&deployment, cancellable, error))
GCancellable *cancellable,
GError **error)
{
+ if (!_ostree_sysroot_ensure_writable (self, error))
+ return FALSE;
+
OstreeDeployment *booted_deployment = ostree_sysroot_get_booted_deployment (self);
if (booted_deployment == NULL)
return glnx_throw (error, "Cannot stage a deployment when not currently booted into an OSTree system");
GCancellable *cancellable,
GError **error)
{
+ if (!_ostree_sysroot_ensure_writable (self, error))
+ return FALSE;
+
/* For now; instead of this do a redeployment */
g_assert (!ostree_deployment_is_staged (deployment));
GCancellable *cancellable,
GError **error)
{
+ if (!_ostree_sysroot_ensure_writable (self, error))
+ return FALSE;
if (g_cancellable_set_error_if_cancelled (cancellable, error))
return FALSE;
OSTREE_SYSROOT_DEBUG_TEST_STAGED_PATH = 1 << 3,
} OstreeSysrootDebugFlags;
+typedef enum {
+ OSTREE_SYSROOT_LOAD_STATE_NONE, /* ostree_sysroot_new() was called */
+ OSTREE_SYSROOT_LOAD_STATE_INIT, /* We've loaded basic sysroot state and have an fd */
+ OSTREE_SYSROOT_LOAD_STATE_LOADED, /* We've loaded all of the deployments */
+} OstreeSysrootLoadState;
+
/**
* OstreeSysroot:
* Internal struct
int sysroot_fd;
GLnxLockFile lock;
- gboolean loaded;
+ OstreeSysrootLoadState loadstate;
+ gboolean mount_namespace_in_use; /* TRUE if caller has told us they used CLONE_NEWNS */
gboolean root_is_ostree_booted; /* TRUE if sysroot is / and we are booted via ostree */
/* The device/inode for /, used to detect booted deployment */
dev_t root_device;
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/"
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development"
+gboolean
+_ostree_sysroot_ensure_writable (OstreeSysroot *self,
+ GError **error);
+
void
_ostree_sysroot_emit_journal_msg (OstreeSysroot *self,
const char *msg);
return ostree_sysroot_new (NULL);
}
+/**
+ * ostree_sysroot_set_mount_namespace_in_use:
+ *
+ * If this function is invoked, then libostree will assume that
+ * a private Linux mount namespace has been created by the process.
+ * The primary use case for this is to have e.g. /sysroot mounted
+ * read-only by default.
+ *
+ * If this function has been called, then when a function which requires
+ * writable access is invoked, libostree will automatically remount as writable
+ * any mount points on which it operates. This currently is just `/sysroot` and
+ * `/boot`.
+ *
+ * If you invoke this function, it must be before ostree_sysroot_load(); it may
+ * be invoked before or after ostree_sysroot_initialize().
+ *
+ * Since: 2019.7
+ */
+void
+ostree_sysroot_set_mount_namespace_in_use (OstreeSysroot *self)
+{
+ /* Must be before we're loaded, as otherwise we'd have to close/reopen all our
+ fds, e.g. the repo */
+ g_return_if_fail (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_LOADED);
+ self->mount_namespace_in_use = TRUE;
+}
+
/**
* ostree_sysroot_get_path:
* @self:
return self->path;
}
+/* Open a directory file descriptor for the sysroot if we haven't yet */
static gboolean
ensure_sysroot_fd (OstreeSysroot *self,
GError **error)
return TRUE;
}
+/* Remount /sysroot read-write if necessary */
+gboolean
+_ostree_sysroot_ensure_writable (OstreeSysroot *self,
+ GError **error)
+{
+ /* Do nothing if no mount namespace is in use */
+ if (!self->mount_namespace_in_use)
+ return TRUE;
+
+ /* If a mount namespace is in use, ensure we're initialized */
+ if (!ostree_sysroot_initialize (self, error))
+ return FALSE;
+
+ /* If we aren't operating on a booted system, then we don't
+ * do anything with mounts.
+ */
+ if (!self->root_is_ostree_booted)
+ return TRUE;
+
+ /* Check if /sysroot is a read-only mountpoint */
+ struct statvfs stvfsbuf;
+ if (statvfs ("/sysroot", &stvfsbuf) < 0)
+ return glnx_throw_errno_prefix (error, "fstatvfs(/sysroot)");
+ if ((stvfsbuf.f_flag & ST_RDONLY) == 0)
+ return TRUE;
+
+ /* OK, let's remount writable. */
+ if (mount ("/sysroot", "/sysroot", NULL, MS_REMOUNT | MS_RELATIME, "") < 0)
+ return glnx_throw_errno_prefix (error, "Remounting /sysroot read-write");
+
+ /* Reopen our fd */
+ glnx_close_fd (&self->sysroot_fd);
+ if (!ensure_sysroot_fd (self, error))
+ return FALSE;
+
+ return TRUE;
+}
+
/**
* ostree_sysroot_get_fd:
* @self: Sysroot
*
- * Access a file descriptor that refers to the root directory of this
- * sysroot. ostree_sysroot_load() must have been invoked prior to
- * calling this function.
+ * Access a file descriptor that refers to the root directory of this sysroot.
+ * ostree_sysroot_initialize() (or ostree_sysroot_load()) must have been invoked
+ * prior to calling this function.
*
* Returns: A file descriptor valid for the lifetime of @self
*/
return self->sysroot_fd;
}
+/**
+ * ostree_sysroot_is_booted:
+ * @self: Sysroot
+ *
+ * Can only be invoked after `ostree_sysroot_initialize()`.
+ *
+ * Returns: %TRUE iff the sysroot points to a booted deployment
+ * Since: 2019.7
+ */
+gboolean
+ostree_sysroot_is_booted (OstreeSysroot *self)
+{
+ g_return_val_if_fail (self->loadstate >= OSTREE_SYSROOT_LOAD_STATE_INIT, FALSE);
+ return self->root_is_ostree_booted;
+}
+
gboolean
_ostree_sysroot_bump_mtime (OstreeSysroot *self,
GError **error)
return TRUE;
}
+/**
+ * ostree_sysroot_initialize:
+ * @self: sysroot
+ *
+ * Subset of ostree_sysroot_load(); performs basic initialization. Notably, one
+ * can invoke `ostree_sysroot_get_fd()` after calling this function.
+ *
+ * It is not necessary to call this function if ostree_sysroot_load() is
+ * invoked.
+ *
+ * Since: 2019.7
+ */
+gboolean
+ostree_sysroot_initialize (OstreeSysroot *self,
+ GError **error)
+{
+ if (!ensure_sysroot_fd (self, error))
+ return FALSE;
+
+ if (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_INIT)
+ {
+ /* Gather some global state; first if we have the global ostree-booted flag;
+ * we'll use it to sanity check that we found a booted deployment for example.
+ * Second, we also find out whether sysroot == /.
+ */
+ if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error))
+ return FALSE;
+ const gboolean ostree_booted = (errno == 0);
+
+ { struct stat root_stbuf;
+ if (!glnx_fstatat (AT_FDCWD, "/", &root_stbuf, 0, error))
+ return FALSE;
+ self->root_device = root_stbuf.st_dev;
+ self->root_inode = root_stbuf.st_ino;
+ }
+
+ struct stat self_stbuf;
+ if (!glnx_fstatat (AT_FDCWD, gs_file_get_path_cached (self->path), &self_stbuf, 0, error))
+ return FALSE;
+
+ const gboolean root_is_sysroot =
+ (self->root_device == self_stbuf.st_dev &&
+ self->root_inode == self_stbuf.st_ino);
+
+ self->root_is_ostree_booted = (ostree_booted && root_is_sysroot);
+ self->loadstate = OSTREE_SYSROOT_LOAD_STATE_INIT;
+ }
+
+ return TRUE;
+}
+
/* Reload the staged deployment from the file in /run */
gboolean
_ostree_sysroot_reload_staged (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
- if (!ensure_sysroot_fd (self, error))
+ if (!ostree_sysroot_initialize (self, error))
return FALSE;
/* Here we also lazily initialize the repository. We didn't do this
if (!ensure_repo (self, error))
return FALSE;
- /* Gather some global state; first if we have the global ostree-booted flag;
- * we'll use it to sanity check that we found a booted deployment for example.
- * Second, we also find out whether sysroot == /.
- */
- if (!self->loaded)
- {
- if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error))
- return FALSE;
- const gboolean ostree_booted = (errno == 0);
-
- { struct stat root_stbuf;
- if (!glnx_fstatat (AT_FDCWD, "/", &root_stbuf, 0, error))
- return FALSE;
- self->root_device = root_stbuf.st_dev;
- self->root_inode = root_stbuf.st_ino;
- }
-
- struct stat self_stbuf;
- if (!glnx_fstat (self->sysroot_fd, &self_stbuf, error))
- return FALSE;
-
- const gboolean root_is_sysroot =
- (self->root_device == self_stbuf.st_dev &&
- self->root_inode == self_stbuf.st_ino);
-
- self->root_is_ostree_booted = (ostree_booted && root_is_sysroot);
- }
-
int bootversion = 0;
if (!read_current_bootversion (self, &bootversion, cancellable, error))
return FALSE;
ostree_deployment_set_index (deployment, i);
}
- /* Determine whether we're "physical" or not, the first time we initialize */
- if (!self->loaded)
+ /* Determine whether we're "physical" or not, the first time we load deployments */
+ if (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_LOADED)
{
/* If we have a booted deployment, the sysroot is / and we're definitely
* not physical.
self->is_physical = TRUE;
}
/* Otherwise, the default is FALSE */
+
+ self->loadstate = OSTREE_SYSROOT_LOAD_STATE_LOADED;
}
self->bootversion = bootversion;
self->subbootversion = subbootversion;
self->deployments = deployments;
deployments = NULL; /* Transfer ownership */
- self->loaded = TRUE;
self->loaded_ts = stbuf.st_mtim;
if (out_changed)
OstreeDeployment *
ostree_sysroot_get_booted_deployment (OstreeSysroot *self)
{
- g_return_val_if_fail (self->loaded, NULL);
+ g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
return self->booted_deployment;
}
OstreeDeployment *
ostree_sysroot_get_staged_deployment (OstreeSysroot *self)
{
- g_return_val_if_fail (self->loaded, NULL);
+ g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
return self->staged_deployment;
}
GPtrArray *
ostree_sysroot_get_deployments (OstreeSysroot *self)
{
- g_return_val_if_fail (self->loaded, NULL);
+ g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
GPtrArray *copy = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
for (guint i = 0; i < self->deployments->len; i++)
* @self: Sysroot
*
* This function is a variant of ostree_sysroot_get_repo() that cannot fail, and
- * returns a cached repository. Can only be called after ostree_sysroot_load()
- * has been invoked successfully.
+ * returns a cached repository. Can only be called after ostree_sysroot_initialize()
+ * or ostree_sysroot_load() has been invoked successfully.
*
* Returns: (transfer none): The OSTree repository in sysroot @self.
*
OstreeRepo *
ostree_sysroot_repo (OstreeSysroot *self)
{
- g_return_val_if_fail (self->loaded, NULL);
+ g_return_val_if_fail (self->loadstate >= OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
g_assert (self->repo);
return self->repo;
}
{
if (!ensure_sysroot_fd (self, error))
return FALSE;
+
+ if (!_ostree_sysroot_ensure_writable (self, error))
+ return FALSE;
+
return glnx_make_lock_file (self->sysroot_fd, OSTREE_SYSROOT_LOCKFILE,
LOCK_EX, &self->lock, error);
}
gboolean *out_acquired,
GError **error)
{
- g_autoptr(GError) local_error = NULL;
-
if (!ensure_sysroot_fd (self, error))
return FALSE;
+ if (!_ostree_sysroot_ensure_writable (self, error))
+ return FALSE;
+
/* Note use of LOCK_NB */
+ g_autoptr(GError) local_error = NULL;
if (!glnx_make_lock_file (self->sysroot_fd, OSTREE_SYSROOT_LOCKFILE,
LOCK_EX | LOCK_NB, &self->lock, &local_error))
{
GCancellable *cancellable,
GError **error)
{
- if (!ensure_sysroot_fd (self, error))
+ if (!_ostree_sysroot_ensure_writable (self, error))
return FALSE;
const char *deploydir = glnx_strjoina ("ostree/deploy/", osname);
_OSTREE_PUBLIC
OstreeSysroot* ostree_sysroot_new_default (void);
+_OSTREE_PUBLIC
+void ostree_sysroot_set_mount_namespace_in_use (OstreeSysroot *self);
+
_OSTREE_PUBLIC
GFile *ostree_sysroot_get_path (OstreeSysroot *self);
+_OSTREE_PUBLIC
+gboolean ostree_sysroot_is_booted (OstreeSysroot *self);
+
_OSTREE_PUBLIC
int ostree_sysroot_get_fd (OstreeSysroot *self);
+_OSTREE_PUBLIC
+gboolean ostree_sysroot_initialize (OstreeSysroot *self,
+ GError **error);
+
_OSTREE_PUBLIC
gboolean ostree_sysroot_load (OstreeSysroot *self,
GCancellable *cancellable,
_OSTREE_PUBLIC
gboolean ostree_sysroot_lock (OstreeSysroot *self, GError **error);
+
+_OSTREE_PUBLIC
+gboolean ostree_sysroot_lock_with_mount_namespace (OstreeSysroot *self, GError **error);
+
_OSTREE_PUBLIC
gboolean ostree_sysroot_try_lock (OstreeSysroot *self,
gboolean *out_acquired,
#include <stdlib.h>
#include <string.h>
+#include <sys/statvfs.h>
#include "ot-main.h"
#include "ostree.h"
sysroot_path = g_file_new_for_path (opt_sysroot);
g_autoptr(OstreeSysroot) sysroot = ostree_sysroot_new (sysroot_path);
+ if (!ostree_sysroot_initialize (sysroot, error))
+ return FALSE;
g_signal_connect (sysroot, "journal-msg", G_CALLBACK (on_sysroot_journal_msg), NULL);
if ((flags & OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED) == 0)
{
+ /* If we're requested to lock the sysroot, first check if we're operating
+ * on a booted (not physical) sysroot. Then find out if the /sysroot
+ * subdir is a read-only mount point, and if so, create a new mount
+ * namespace and tell the sysroot that we've done so. See the docs for
+ * ostree_sysroot_set_mount_namespace_in_use().
+ *
+ * This is a conservative approach; we could just always
+ * unshare() too.
+ */
+ if (ostree_sysroot_is_booted (sysroot))
+ {
+ int sysroot_fd = ostree_sysroot_get_fd (sysroot);
+ g_assert_cmpint (sysroot_fd, !=, -1);
+
+ glnx_autofd int sysroot_subdir_fd = glnx_opendirat_with_errno (sysroot_fd, "sysroot", TRUE);
+ if (sysroot_subdir_fd < 0)
+ {
+ if (errno != ENOENT)
+ return glnx_throw_errno_prefix (error, "opendirat");
+ }
+ else if (getuid () == 0)
+ {
+ struct statvfs stvfs;
+ if (fstatvfs (sysroot_subdir_fd, &stvfs) < 0)
+ return glnx_throw_errno_prefix (error, "fstatvfs");
+ if (stvfs.f_flag & ST_RDONLY)
+ {
+ if (unshare (CLONE_NEWNS) < 0)
+ return glnx_throw_errno_prefix (error, "preparing writable sysroot: unshare (CLONE_NEWNS)");
+ ostree_sysroot_set_mount_namespace_in_use (sysroot);
+ }
+ }
+ }
+
/* Released when sysroot is finalized, or on process exit */
if (!ot_admin_sysroot_lock (sysroot, error))
return FALSE;
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
+#include <stdbool.h>
#include <stdint.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <err.h>
#include <errno.h>
+#include <glib.h>
+
#include "ostree-mount-util.h"
+#include "glnx-backport-autocleanups.h"
static void
-do_remount (const char *target)
+do_remount (const char *target,
+ bool writable)
{
struct stat stbuf;
if (lstat (target, &stbuf) < 0)
struct statvfs stvfsbuf;
if (statvfs (target, &stvfsbuf) == -1)
return;
- /* If no read-only flag, skip it */
- if ((stvfsbuf.f_flag & ST_RDONLY) == 0)
+
+ const bool currently_writable = ((stvfsbuf.f_flag & ST_RDONLY) == 0);
+ if (writable == currently_writable)
return;
- /* It's a mounted, read-only fs; remount it */
- if (mount (target, target, NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
+
+ int mnt_flags = MS_REMOUNT | MS_SILENT;
+ if (!writable)
+ mnt_flags |= MS_RDONLY;
+ if (mount (target, target, NULL, mnt_flags, NULL) < 0)
{
/* Also ignore EINVAL - if the target isn't a mountpoint
* already, then assume things are OK.
*/
- if (errno != EINVAL)
- err (EXIT_FAILURE, "failed to remount %s", target);
+ if (errno != EINVAL)
+ err (EXIT_FAILURE, "failed to remount(%s) %s", writable ? "rw" : "ro", target);
+ else
+ return;
}
- else
- printf ("Remounted: %s\n", target);
+
+ printf ("Remounted %s: %s\n", writable ? "rw" : "ro", target);
+}
+
+static bool
+sysroot_is_configured_ro (void)
+{
+ struct stat stbuf;
+ static const char config_path[] = "/ostree/repo/config";
+ if (stat (config_path, &stbuf) != 0)
+ return false;
+
+ g_autoptr(GKeyFile) keyfile = g_key_file_new ();
+ if (!g_key_file_load_from_file (keyfile, config_path, 0, NULL))
+ return false;
+
+ return g_key_file_get_boolean (keyfile, "sysroot", "readonly", NULL);
}
int
exit (EXIT_SUCCESS);
}
- do_remount ("/sysroot");
- do_remount ("/var");
+ /* Query the repository configuration - this is an operating system builder
+ * choice.
+ * */
+ const bool sysroot_readonly = sysroot_is_configured_ro ();
+
+ /* Mount the sysroot read-only if we're configured to do so.
+ * Note we only get here if / is already writable.
+ */
+ do_remount ("/sysroot", !sysroot_readonly);
+
+ if (sysroot_readonly)
+ {
+ /* Now, /etc is not normally a bind mount, but remounting the
+ * sysroot above made it read-only since it's on the same filesystem.
+ * Make it a self-bind mount, so we can then mount it read-write.
+ */
+ if (mount ("/etc", "/etc", NULL, MS_BIND, NULL) < 0)
+ err (EXIT_FAILURE, "failed to make /etc a bind mount");
+ do_remount ("/etc", true);
+ }
+
+ /* If /var was created as as an OSTree default bind mount (instead of being a separate filesystem)
+ * then remounting the root mount read-only also remounted it.
+ * So just like /etc, we need to make it read-write by default.
+ * If it was a separate filesystem, we expect it to be writable anyways,
+ * so it doesn't hurt to remount it if so.
+ *
+ * And if we started out with a writable system root, then we need
+ * to ensure that the /var bind mount created by the systemd generator
+ * is writable too.
+ */
+ do_remount ("/var", true);
exit (EXIT_SUCCESS);
}